#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <GL/glut.h>

#include <port_comm.h>

#define VIEWPORT_WIDTH 640
#define VIEWPORT_HEIGHT 480
#define KEY_Q 113

int initGL();

GLvoid OnDisplay();
GLvoid OnReshape(GLint w, GLint h);
GLvoid OnKeyPress(unsigned char key, GLint x, GLint y);
GLvoid OnIdle();
void printUsage();

GLint g_hWindow;
PortComm *commServer;
char* dest;
int quality;
bool grey;
int main_socket;

int main(int argc, char *argv[])
{
    // get port to run on, and IP+port to communicate with from command line
    if(argc != 5)
    {
        printUsage();
        return 1;
    }
    int port_number = atol(argv[1]);
    char *remote = argv[2];
    int remote_port = atol(argv[3]);

    // Step 1 Look up server to get numeric IP address
    hostent * record = gethostbyname(remote);
    if(record==NULL)
    {
        herror("gethostbyname failed");
        exit(1);
    }
    in_addr * addressptr = (in_addr *) record->h_addr;

    // Step 2 Create a socket
    main_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if(main_socket<0)
    {
        perror("socket creation");
        exit(1);
    }

    // Step 3 Create a sockaddr_in to describe the local port
    sockaddr_in local_info;
    // local_info.sin_len = sizeof(local_info);
    local_info.sin_family = AF_INET;
    local_info.sin_addr.s_addr = htonl(INADDR_ANY);
    local_info.sin_port = htons(port_number);

    // Step 4 Bind the socket to the port
    int r = bind(main_socket, (sockaddr *) &local_info, sizeof(local_info));
    if(r < 0)
    {
        perror("bind");
        exit(1);
    }
    printf("ready\n");

    // Step 5 Create a sockaddr_in to describe the remote application
    sockaddr_in remote_info;
    // remote_info.sin_len = sizeof(remote_info);
    remote_info.sin_family = AF_INET;
    remote_info.sin_addr = *addressptr;
    remote_info.sin_port = htons(remote_port);

    char* file = argv[4];

    if(file[strlen(file)-1] != '/')
    {
        //Cat a / on to the end
        char* new_file = new char[strlen(file) + 1];
        strcpy (new_file, file);

        strcat (new_file, "/");
        file = new_file; 
    }
    const char* name = "test.jpg";
    dest = new char[strlen(file) + strlen(name) + 1];
    strcpy (dest, file);
    strcat (dest, name);
    printf("Saving to %s\n", dest);

    //Init GL
    glutInit(&argc, argv);
    if(initGL() != 0)
    {
        fprintf(stderr, "Unable to init GL Component.\n");
        return 1;
    }

    printf("Init GL complete.\n");

    quality = 0;
    grey = false;

    //Run event loop
    glutMainLoop();
    return 0;
}

int initGL()
{
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    g_hWindow = glutCreateWindow("2012 Rover Team");

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glutDisplayFunc(OnDisplay);
    glutReshapeFunc(OnReshape);
    glutKeyboardFunc(OnKeyPress);
    glutIdleFunc(OnIdle);

    return 0;
}

GLvoid OnDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex2f(VIEWPORT_WIDTH, 0.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex2f(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
    glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, VIEWPORT_HEIGHT);
    glEnd();

    glFlush();
    glutSwapBuffers();
}

GLvoid OnReshape(GLint w, GLint h)
{
    glViewport(0, 0, w, h);
}

GLvoid OnKeyPress(unsigned char key, int x, int y)
{
    switch(key)
    {
        case KEY_Q:
            glutDestroyWindow(g_hWindow);
            exit(0);
        case 'g':
            grey = !grey;
            break;
        case 'l':
            quality++;
            if(quality > 100)
            {
                quality = 100;
            }
            break;
        case 'k':
            quality += 10;
            if(quality > 100)
            {
                quality = 100;
            }
            break;
        case 'h':
            quality--;
            if(quality < 0)
            {
                quality = 0;
            }
            break;
        case 'j':
            quality -= 10;
            if(quality < 0)
            {
                quality = 0;
            }
            break;
    }

    glutPostRedisplay();
}

GLvoid OnIdle()
{
    int SIZE = 100000;
    char message[SIZE];
    struct sockaddr_in incoming_info;
    unsigned int socklen = sizeof(incoming_info);
    int r = recvfrom(main_socket, &message, SIZE, 0, (sockaddr *) &incoming_info, &socklen);
    if(r < 0)
    {
        perror("recvfrom");
    }
    printf("%d\n", r);

    //Convert to a vector in order to convert
    cv::vector<uchar> buf;
    for(int i = 0; i < r; ++i)
    {
        buf.push_back((uchar)message[i]);
    }

    //Incoming image is in jpg, convert to Mat
    cv::Mat matrix = cv::imdecode(buf, 1);

    //Save image
    cv::imwrite(dest, matrix);

    //Convert to GL RGB format
    cv::cvtColor(matrix, matrix, CV_BGR2RGB);
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, matrix.cols, matrix.rows, GL_RGB, GL_UNSIGNED_BYTE, matrix.data);
  
    glutPostRedisplay();
}

void printUsage()
{
    printf("usage: server <localport> <hostname> <remoteport> <save_loc>\n");
    return;
}

